#include "OpenCVImage.h"
#include "Color.h"

#include <iterator>

using namespace std;

// Constructs an empty image.
OpenCVImage::OpenCVImage() : image_() {}

// Copies the image.
OpenCVImage::OpenCVImage(const OpenCVImage& rhs) : image_() {
  rhs.image_.copyTo(image_);
}

// Copies the image.
OpenCVImage& OpenCVImage::operator=(const OpenCVImage& rhs) {
  rhs.image_.copyTo(image_);
  return *this;
}

// Loads an image from specified file.
int OpenCVImage::load(const string& filename) {
  // Load 3-channel image.
  cv::Mat_<cv::Vec3b> image = cv::imread(filename, 1);

  // Image was loaded successfully.
  image_ = image;
  return 0;
}

// Save the image.
int OpenCVImage::save(const string& filename) const {
  bool result = cv::imwrite(filename, image_);

  if (!result) {
    // Saving the image failed.
    return -1;
  }

  // Save succeeded.
  return 0;
}

// Loads an image from raw data.
int OpenCVImage::loadFromStream(istream& stream) {
  // Do not skip whitespace.
  stream >> noskipws;
  // Beginning and end of stream.
  istream_iterator<unsigned char> begin(stream);
  istream_iterator<unsigned char> end;

  // Buffer to copy contents into.
  vector<unsigned char> buffer;
  copy(begin, end, back_inserter(buffer));

  // Load 3-channel image.
  cv::Mat raw(buffer);
  cv::Mat_<cv::Vec3b> image(cv::imdecode(raw, 1));

  image_ = image;
  return 0;
}

// Creates an empty image of given dimension with background color.
int OpenCVImage::create(int width, int height, const Color& background) {
  // Construct a vector representing the background color.
  cv::Vec3b fill(background.red(), background.green(), background.blue());
  // Create an empty image.
  image_ = cv::Mat_<cv::Vec3b>(height, width, fill);

  return 0;
}

// Returns the image width.
int OpenCVImage::width() const {
  return image_.size().width;
}

// Returns the image height.
int OpenCVImage::height() const {
  return image_.size().height;
}

// Returns the color of a pixel.
Color OpenCVImage::pixel(int i, int j) const {
  // Get pixel color.
  cv::Vec3b color(image_(i, j));
  // Construct and return a color.
  return Color(color[0], color[1], color[2]);
}

// Sets the color of a pixel.
void OpenCVImage::setPixel(int i, int j, const Color& color) {
  // Set pixel color.
  image_(i, j) = cv::Vec3b(color.red(), color.green(), color.blue());
}

void OpenCVImage::composite(const OpenCVImage& other, int i, int j) {
  // Constrain dimensions to size of output image.
  int width = min(other.width(), this->width() - j);
  int height = min(other.height(), this->height() - i);

  // Constrained region to copy to and from in each image.
  cv::Rect target_rect(j, i, width, height);
  cv::Rect source_rect(0, 0, width, height);

  // Copy images.
  cv::Mat target(image_(target_rect));
  cv::Mat source(other.image_(source_rect));
  source.copyTo(target);
}

